镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

build(web): generate the UI's Tailwind CSS with the pinned standalone CLI - #685

Merged
ChuckBuilds merged 4 commits into
mainfrom
claude/tailwind-build
Sep 30, 2026
Merged

ChuckBuilds merged 4 commits into
mainfrom
claude/tailwind-build

Conversation

@ChuckBuilds

Copy link
Copy Markdown
Owner

Summary

Replaces the hand-written Tailwind subset in static/v3/app.css with a real, purged Tailwind build, per the owner decision on the Sept 2026 web UI audit (P0, "undefined utility layer"): standalone Tailwind CLI, no Node, generated CSS committed, the Pi never builds anything.

  • scripts/build_css.py downloads the pinned Tailwind v3.4.19 standalone CLI for the current OS/CPU (Linux x64/arm64/armv7, macOS, Windows), checks it against a pinned SHA-256, caches it outside the repo, and builds two files. --check builds to a temp dir and fails if the committed files differ.
  • static/v3/tailwind.css (generated, committed) holds the utilities. Content globs: templates/v3/**/*.html, static/v3/**/*.js (excluding vendored *.min.js) and blueprints/**/*.py. A safelist covers the one runtime-assembled family (bg-${color}-50 etc. in tools.html). darkMode is ['selector', '[data-theme="dark"]'], matching how the app switches themes.
  • app.css loses ~515 duplicated utility rules and keeps what is actually custom: tokens, base styles, components, dark-theme overrides and the end-of-file guards. It loads after tailwind.css, so components and [data-theme="dark"] overrides still win where they did. The values app.css had customised live in the Tailwind theme (web_interface/tailwind/tailwind.config.js), so responsive and state variants use them too: darker light-mode gray text, emerald/amber bg-green-*/bg-yellow-*, font-size line-heights, token shadows, the narrow transition, and ring-offset colour. focus:outline-none and peer-focus:* are blocklisted so app.css's keyboard-only (:focus-visible) versions stay the only ones.
  • Plugin web_ui/ iframes (/v3/plugin-ui/...) no longer load Tailwind v2.2.19 from cdnjs, which fails in AP mode. They link a local static/v3/plugin-frame.css: preflight plus a safelist of the common utility families, using v2's palette (green/yellow/purple = emerald/amber/violet). The official of-the-day fragment renders pixel-identical to the CDN version apart from its spinner.
  • CI: new css-build job ("Tailwind CSS is up to date") in test.yml runs python scripts/build_css.py --check.
  • Why v3, not v4: v4 drops bg-opacity-* and flex-shrink-* (both used here), switches the palette to oklch (every colour would shift), and needs Safari 16.4+/Chrome 111+.

State of main vs. the audit

The audit's "333 classes with no rule / .hidden missing" predates #568, which hand-filled most of the gaps. So the visual change here is smaller than the audit implies. What this PR fixes on top of main:

  1. Border utilities had no style. On main border-b, border-t, divide-y (and .border only by per-rule patching) set a width with no border-style, so tab-row underlines, section dividers and table/list dividers never drew. The build adds preflight's *{border-width:0;border-style:solid} reset (plus hr and native text fields keeping a 1px border).
  2. Every 2xl: class was dead. app.css wrote .2xl\:grid-cols-4, which is invalid CSS because of the unescaped leading digit. Tailwind escapes it, so at 1536px and wider the plugin grids go to 5 columns and the gutters widen, as the markup says.
  3. The scroll-hint rule wiped backgrounds. .overflow-x-auto { background: … } (shorthand) reset background-color, so the live preview stage bg-gray-900 … overflow-x-auto rendered white, and the Tools command-output <pre class="bg-gray-900 text-gray-100 overflow-x-auto"> was light text on no background. The hint now skips elements with their own bg-*.
  4. ~20 classes main never defined now work, among them the teal "configure" badge in Operation History, the button of a purple web_ui_actions card (white text on no background), the text-indigo-600 disk icon on Tools, toggle-switch knob offsets (translate-x-4/7), slider accents, password-strength colours, -mb-px on the tab rows, and resize-* on the textarea widget.
  5. Source-order bugs: duration-*/ease-* now override transition-* and pr-20 overrides px-3 (plugin search field), as written.
  6. space-y-* on <p> children now zeroes the UA bottom margin (Tailwind's rule), so the plugin-card metadata rows use the 6px gap the markup asks for instead of ~14px.

Plugin compatibility (a PRODUCT.md hard constraint): third-party widget JS renders into the page and can't be scanned, so the config safelists the ~17 utilities main defined that core no longer uses. The generated set is a superset of what worked before. I checked the one official plugin widget (soccer-scoreboard/widgets/custom-leagues.js); every utility it uses is present.

Visual check (before = origin/main c3a7a110, after = this branch)

Headless Chrome, full-page, 8 pages × {1280px, 375px} × {light, dark} = 32 pairs each side. They're saved locally (not committed) in the agent's scratchpad shots/{before,after2,compare2}; compare2/ holds before | after | diff-mask composites.

Page Change Regressions
Overview Section dividers and tab-row underline now draw; the live preview stage is dark (bg-gray-900) as the markup specifies, instead of white none
Plugin Manager Dividers under the header and "Installed Plugins"; card metadata rows tighter (space-y-1.5 now as specified); page ~57px shorter; store unchanged none
Plugin config (Web UI Info) Header divider now draws; native number input has a 1px solid border instead of the 2px inset UA border none
Display Header divider draws. The simulation-mode banner appeared in one run and not the other on both builds (driven by /api/v3/hardware/status, not CSS) none
Fonts Dividers only (~2.7% of pixels) none
Logs Dividers only (~2%) none
WiFi Divider under the description (~3%) none
Tools Row dividers inside each card now draw; Disk icon indigo instead of black; git panel differs by checkout state only none
Dark theme Same changes; dividers use --color-border and read subtly none
375px Same changes; drawer and header unchanged none

Environment noise excluded: live CPU/memory numbers, git branch/dirty state, SSE preview timing, and an "update available" banner in the first after-run (that server was one commit behind; I re-shot after rebasing).

Not fixed here (pre-existing, same on main): the "Logs tab" link in the Display hardware banner has no colour class, so it's browser-default blue (poor on the dark amber box); the Starlark Apps card title is overlapped by its toggle.

Payload (gzip level 6, Flask-Compress's default)

raw gzip
before: app.css 71,274 B 15,507 B
after: app.css 42,725 B 10,027 B
after: tailwind.css 38,575 B 6,759 B
after total (2 requests) 81,300 B 16,786 B (+1.3 KB)
plugin-frame.css (iframes only; replaces the full v2 CDN build) 121,925 B 15,260 B

Tests

  • New: test/test_build_css_script.py (pins are 64-hex SHA-256; per-OS/CPU asset mapping; unsupported CPU errors clearly; a corrupt cached CLI is replaced and a tampered download is rejected). test_web_static_audit.py now reads tailwind.css + app.css (CSS-unescaping \32xl\:), and asserts the runtime-built colour classes exist and that tailwind.css loads before app.css. test_pages_v3_partials.py asserts the plugin iframe links the local file and no CDN. Smoke/gzip tests cover the new assets.
  • build_css.py --check mutation-checked: a new class in a template makes it exit 1. Five repeat builds are byte-identical, and reordering the content globs doesn't change the output.
  • Cross-OS parity: the first CI run of --check failed because a Windows checkout (core.autocrlf) has a CRLF app.input.css, and Tailwind's minifier merges rules differently for it (same rules, different bytes). The content files' line endings turned out not to matter. build_css.py now feeds the CLI an LF copy of the input (with a test for it), and the committed Windows-built CSS passes --check on CI's Linux runner.
  • Full suite (pytest -m "not hardware" test/ --ignore=test/plugins) on Windows, diffed against an origin/main baseline worktree: identical FAILED/ERROR sets (59 failed + 6 errors on both, the known Windows-only set); branch 6116 passed vs baseline 6098 (the new tests).
  • JS suites (REQUIRE_DOM=1 node test/js/run_all.js against a live server): all suites pass on both baseline and branch (499 ok each).

Contributor impact

Change a template or static JS → run python3 scripts/build_css.py and commit static/v3/tailwind.css; CI tells you if you forgot. Documented in web_interface/README.md (new "Styling (Tailwind CSS)" section), CONTRIBUTING.md, the widgets README, and docs/WEB_INTERFACE_GUIDE.md.

🤖 Generated with Claude Code

… CLI

The web UI is written in Tailwind classes but had no Tailwind build:
app.css hand-maintained ~500 utility rules. Replace them with a purged
build from the standalone Tailwind CLI (v3.4.19, no Node), committed so
the Pi never builds anything.

- scripts/build_css.py downloads the CLI for the current OS/CPU, checks
  its pinned SHA-256, caches it outside the repo and builds
  static/v3/tailwind.css (+ plugin-frame.css). --check fails when the
  committed CSS is stale; a new CI job runs it.
- Content globs cover templates/v3, static/v3 JS and blueprints; a
  safelist covers the runtime-built colour classes in tools.html and the
  utilities app.css defined that core no longer uses (plugin widgets may).
- darkMode matches <html data-theme="dark">. The values app.css had
  customised (gray text, emerald/amber fills, font line-heights, token
  shadows, transition, ring offset) move into the theme; keyboard-only
  focus rules stay in app.css with Tailwind's versions blocklisted.
- app.css keeps tokens, base, components and dark overrides and loads
  after tailwind.css, so its rules still win where they did.
- Border utilities now get preflight's width/style reset, so border-b,
  border-t and divide-y draw. 2xl: classes work (the hand-written
  selectors were invalid CSS). The scroll-hint rule no longer wipes the
  background of elements with their own bg-*.
- Plugin web_ui iframes link a local plugin-frame.css instead of the
  Tailwind v2 CDN, which failed in AP mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 7 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 18553812-59de-409f-9b41-8eb8fd402e6f

📥 Commits

Reviewing files that changed from the base of the PR and between 5ea0d51 and 519332b.

📒 Files selected for processing (23)
  • .gitattributes
  • .github/workflows/test.yml
  • CHANGELOG.md
  • CONTRIBUTING.md
  • docs/WEB_INTERFACE_GUIDE.md
  • scripts/build_css.py
  • test/test_build_css_script.py
  • test/test_pages_v3_partials.py
  • test/test_web_smoke.py
  • test/test_web_static_audit.py
  • test/web_interface/test_gzip_fallback.py
  • web_interface/README.md
  • web_interface/blueprints/pages_v3.py
  • web_interface/static/v3/app.css
  • web_interface/static/v3/js/widgets/README.md
  • web_interface/static/v3/plugin-frame.css
  • web_interface/static/v3/tailwind.css
  • web_interface/tailwind/app.input.css
  • web_interface/tailwind/plugin-frame.config.js
  • web_interface/tailwind/plugin-frame.input.css
  • web_interface/tailwind/tailwind.config.js
  • web_interface/templates/v3/base.html
  • web_interface/templates/v3/login.html

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codacy-production

codacy-production Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 240 complexity · 0 duplication

Metric Results
Complexity 240
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

ChuckBuilds and others added 3 commits September 29, 2026 19:52
… in the plugin-frame safelist

- build_css.py refuses a non-https download URL before urlopen (which
  also follows file:// and custom schemes), with a test. The subprocess
  import and the CLI run are annotated: list-form argv, no shell, the CLI
  path is computed locally and the binary is SHA-256-verified.
- plugin-frame.config.js builds its safelist from regex literals instead
  of new RegExp() over in-file constants (ESLint security-node
  non-literal-reg-expr). The pattern sources are unchanged, and
  build_css.py --check still reports the committed CSS up to date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
app.css no longer holds the utility classes (they moved to the generated
tailwind.css), and login.html (#683) links only app.css, so the login page
rendered almost unstyled once both PRs were on main. Link tailwind.css
before app.css, as base.html does, and extend the load-order test to every
template that links app.css.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ChuckBuilds
ChuckBuilds merged commit ba6eccb into main Sep 30, 2026
13 checks passed
@ChuckBuilds
ChuckBuilds deleted the claude/tailwind-build branch September 30, 2026 13:38
ChuckBuilds added a commit that referenced this pull request Sep 30, 2026
Conflicts:
- CHANGELOG.md: kept both; "Update channels" first, then main's sections.
- docs/ARCHITECTURE.md: the verifier paragraph now says both things --
  the heartbeat check from #687 and the return to old_ref from this branch.
- web_interface/blueprints/pages_v3.py: the General partial gets both
  update_channel_status (this branch) and web_login (#683).

Follow-ups for main's changes:
- #688: the channel API test fixture stubs api_v3.plugin_catalog, not the
  retired plugin_manager.
- #683: test_web_auth pins that GET/POST /system/update-channel need login
  from the LAN when login is on (the default rule; mutation-checked by
  exempting the endpoints).
- #685: scripts/build_css.py regenerates identical CSS; --check passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ChuckBuilds added a commit that referenced this pull request Sep 30, 2026
…#684)

Adds auto_update.channel: stable follows the newest vX.Y.Z release tag
(detached HEAD; pre-releases and other tags ignored), beta follows main as
before. Nothing ever moves a device backwards: a checkout newer than the
newest release keeps following main (or stays put when detached) until a
release contains its commit. Legacy configs migrate to stable when they
reach a release. Update Code, the weekly updater's preflight, and the
verifier's rollback (back to old_ref: branch or detached release) all
honour the channel. General tab Update Channel select, GET/POST
/api/v3/system/update-channel, release-aware Overview banner and Tools git
panel. New installs default to stable.

Rig fix (ledpi): /system/check-update reports update_available: false when
the channel's action is none (a detached HEAD newer than the newest
release), matching Update Code; the Tools panel no longer calls every
detached HEAD "a release".

Merged with main through #687 (heartbeat verifier, #683 login, #688
plugin_catalog, #685 Tailwind build).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant